<template>
  <!--时间轴组件-->
  <div ref="timeLineRef" class="time-line-com">
    <RenderScale
      ref="renderScaleRef"
      :video-duration="videoDuration"
      @click.native="clickTimeLine"
    ></RenderScale>
    <RenderTimeLine
      ref="renderTimeLineRef"
      v-model="timeLineArr"
      class="render-time-line"
      @click.native.stop
      @updateTime="handleUpdateTime"
      @dragEnd="handleIndexChange"
      @dropEnd="handleDropEnd"
      @scrollSync="scrollSync"
    ></RenderTimeLine>
    <PreviewPopover ref="previewPopoverRef" />
  </div>
</template>

<script>
import RenderScale from './components/renderScale.vue';
import { deleteUnParse, findDIff, getScaleRate, handleParseConfig } from './js/common';
import { AXIS_OPERATE_LIST, TIMELINE_CONFIG } from './js/enums';
import RenderTimeLine from './components/renderTimeLine.vue';
import { throttle } from 'lodash';
import PreviewPopover from '@veditor/components/timeLine/components/PreviewPopover.vue';
import { isMultiVtuberByScene } from '@veditor/store/modules/protocol/utils';
import { getBlobByUrl } from '@veditor/components/timeLine/js/tools';

export default {
  name: 'TimeLineCom',
  components: { PreviewPopover, RenderScale, RenderTimeLine },
  data() {
    return {
      videoDuration: 0, // 时间轴总时长
      scaleRate: TIMELINE_CONFIG.defaultScaleRate, // 时间轴缩放比例
      timeLineArr: [], // 时间轴对象的数组
      beforeData: {}, // 记录一下之前的配置
      initFlag: false, // 第一次初始化完成标志
      observe: null,
      partUpdateArr: [], // 存储数据更新时更新的时间轴元素
      beforeDuration: 0,
      updateLock: false,
    };
  },
  mounted() {
    this.observeResize();
    document.addEventListener('keydown', this.clickDelete, { capture: true });
  },
  beforeDestroy() {
    if (this.observe && this.$refs.timeLineRef) {
      this.observe.unobserve(this.$refs.timeLineRef);
    }
    document.removeEventListener('keydown', this.clickDelete, { capture: true });
  },
  methods: {
    // 用于 play,pause,replay,drag
    operate({ messageType, data }) {
      if (AXIS_OPERATE_LIST.indexOf(messageType) !== -1) {
        return this.handleAxisOperate({ messageType, data });
      }
      switch (messageType) {
        case 'feed': {
          data = JSON.parse(JSON.stringify(data));
          data.components = deleteUnParse(data.components);
          if (!this.initFlag) {
            this.initFlag = true;
            this.feed(data);
          } else {
            this.updateData(data);
          }
          break;
        }
        case 'scaleRate': {
          this.scaleRateChange(data);
          break;
        }
        case 'selectChange': {
          this.changeSelected(data);
          break;
        }
        default:
          break;
      }
    },
    // 用于投喂数据,初始化数据,后续走的局部更新的算法
    feed(config) {
      const { videoDuration, timeLineArr } = handleParseConfig(config);
      this.$refs.renderTimeLineRef.updateDuration(videoDuration);
      this.timeLineArr.splice(0, this.timeLineArr.length);
      this.timeLineArr.push(...timeLineArr);
      this.handleACTCallback(); // 这里需要给所有新增的元素添加修改时间的回调函数
      this.videoDuration = videoDuration;
      this.beforeData = JSON.parse(JSON.stringify(config));
      this.$nextTick(() => {
        this.$refs.renderScaleRef.calcPosition(); // 更新prop需要时间
      });
    },
    // 用于更新数据
    async updateData(data) {
      if (this.updateLock) {
        return;
      }
      this.updateLock = true;
      const beforeData = JSON.parse(JSON.stringify(this.beforeData)); // 这里可以不用复制,因为数据本身就不用了
      // 这里处理异步音频的问题
      await this.handleTextData(data);
      const updateArr = findDIff(beforeData, data);
      this.beforeData = JSON.parse(JSON.stringify(data));
      this.handleUpdateArr(updateArr); // 单独处理更新数组的
      this.handleACTCallback(); // 这里需要给所有新增的元素添加修改时间的回调函数
      this.updateLock = false;
    },
    // 监听页面大小改变,重新计算页面和所有元素
    observeResize() {
      this.observe = new ResizeObserver(() => {
        this.handleResize();
      });
      this.observe.observe(this.$refs.timeLineRef);
    },
    // 处理页面大小改变
    handleResize() {
      this.$refs.renderScaleRef.calcPosition();
      this.$refs.renderTimeLineRef.resize();
      this.timeLineArr.forEach(item => {
        item.resize();
      });
    },
    // 控制时间轴操作
    handleAxisOperate(message) {
      this.$refs.renderTimeLineRef.handleOperate(message);
    },
    // 处理点击时间轴
    clickTimeLine(e) {
      this.$refs.renderTimeLineRef.clickAxisLine(e); // 这里可以直接返回新的时间
    },
    // 处理更新数组
    // eslint-disable-next-line sonarjs/cognitive-complexity,complexity
    handleUpdateArr(updateArr) {
      let end = false;
      let durationChange = false;
      const { timeLineArr, partUpdateArr } = this;
      partUpdateArr.splice(0, partUpdateArr.length);
      for (let i = 0; i < updateArr.length; i++) {
        const { messageType, data } = updateArr[i];
        switch (messageType) {
          case 'all': {
            this.handleUpdateScene(data);
            end = true;
            break;
          }
          case 'duration': {
            const { duration } = data;
            this.videoDuration = duration;
            durationChange = true;
            break;
          }
          case 'bgMusic': {
            /*
             * 1.老数据存在&新数据存在:直接替换
             * 2.老数据存在,新数据不存在,直接删除
             * 3.老数据不存在,新数据存在,直接push
             * */
            const bgIndex = timeLineArr.findIndex(item => item.name === 'bgMusic');
            if (data && bgIndex !== -1) {
              this.extendBefore(data, timeLineArr[bgIndex]);
              partUpdateArr.push(timeLineArr[bgIndex].mark);
            } else if (bgIndex !== -1 && !data) {
              timeLineArr.splice(bgIndex, 1);
              this.$emit('callback', { messageType: 'deleteMusic' });
            } else if (bgIndex === -1 && data) {
              timeLineArr.push(data);
            }
            break;
          }
          case 'broadcast': {
            const DRIVE_LIST = ['textDrive', 'audioDrive', 'doubleDrive'];
            const [d1, d2] = data;
            const filterBr = timeLineArr.filter(item => DRIVE_LIST.indexOf(item.name) === -1);
            const [b1, b2] = timeLineArr.filter(item => DRIVE_LIST.indexOf(item.name) !== -1);
            if (d1) {
              if (b1 && d1.name === b1.name) {
                this.extendBefore(d1, b1);
                partUpdateArr.push(b1.mark);
                filterBr.push(b1);
              } else {
                filterBr.push(d1);
              }
            }
            if (d2) {
              if (b2 && d2.name === b2.name) {
                this.extendBefore(d2, b2);
                partUpdateArr.push(b2.mark);
                filterBr.push(b2);
              } else {
                filterBr.push(d2);
              }
            }
            timeLineArr.splice(0, timeLineArr.length);
            timeLineArr.push(...filterBr);
            break;
          }
          case 'put': {
            const { arr, mark } = data;
            const [first, ...other] = arr;
            const oldIndex = timeLineArr.findIndex(item => item.mark === mark);
            this.extendBefore(first, timeLineArr[oldIndex]);
            this.handleVtuber(first, other); // 这里判断是数字人然后更新里面的子元素
            partUpdateArr.push(timeLineArr[oldIndex].mark);
            break;
          }
          case 'add': {
            const { arr } = data;
            const [first, ...other] = arr;
            timeLineArr.push(first);
            this.handleVtuber(first, other); //这里判断是数字人然后更新里面的子元素
            break;
          }
          case 'del': {
            const { mark, vtuberType } = data;
            const delIndex = timeLineArr.findIndex(item => item.mark === mark);
            delIndex !== -1 && timeLineArr.splice(delIndex, 1);
            vtuberType && this.delVtuberConfig([], vtuberType); // 删除超写实虚拟人附属的对象
            break;
          }
          case 'zIndex': {
            this.handleZIndexChange(data);
            break;
          }
          default:
            break;
        }
        if (end) {
          break;
        }
      }
      timeLineArr.sort((a, b) => b.zIndex - a.zIndex); // 这里重新对数据根据zIndex排序
      this.handleDurationChange(durationChange); // 更新页面
      if (durationChange) {
        const { beforeDuration, videoDuration, scaleRate } = this;
        this.$refs.renderTimeLineRef.updateDuration(videoDuration);
        const newScale = getScaleRate(beforeDuration, videoDuration, scaleRate) || 1;
        this.$emit('callback', { messageType: 'updateScale', data: newScale });
        this.beforeDuration = videoDuration;
        this.scaleRateChange({ scaleRate: newScale });
      }
    },
    // 用来删除虚拟人动作,3d背景,相机的方法
    delVtuberConfig(second, type) {
      // 动作 3d背景 相机
      const VTUBER_CONFIG_DICT = {
        normal: ['emotion'],
        real: ['action', 'background3d', 'camera', 'aiFinTemplate'],
      };
      const updateConfigArr = VTUBER_CONFIG_DICT[type] || [];
      updateConfigArr.forEach(item => {
        this.changeRealConfig(item, second);
      });
    },
    // 这里是更新scene
    handleUpdateScene(data) {
      const { videoDuration: oldVd } = this;
      const { videoDuration: newVd, timeLineArr } = handleParseConfig(data);
      newVd !== oldVd && this.$refs.renderTimeLineRef.updateDuration(newVd);
      this.videoDuration = newVd;
      this.timeLineArr.splice(0, this.timeLineArr.length);
      this.timeLineArr.push(...timeLineArr);
      this.$nextTick(() => {
        this.$refs.renderScaleRef.calcPosition(); // 更新prop需要时间
      });
    },
    // 这里是处理duration刷新,触发resize方法
    handleDurationChange(isDurationChange) {
      const { timeLineArr, videoDuration, partUpdateArr } = this;
      // 这里判断一下,只刷新那些变化的轴
      timeLineArr.forEach(item => {
        if (isDurationChange) {
          item.updateVideoDuration(videoDuration);
        }
        if (partUpdateArr.indexOf(item.mark) !== -1) {
          item.resize();
          item.onVideoDuration();
        }
      });
    },
    // 处理时间轴的点击
    handleUpdateTime(newTime) {
      this.$emit('callback', { messageType: 'updateTime', data: { time: newTime } });
    },
    // 处理时间轴上下拖动
    handleIndexChange(oldMark, newMark) {
      const message = {
        messageType: 'dragend',
        data: {
          oldMark,
          newMark,
        },
      };
      // 这里需要找这个index对应的mark
      this.$emit('callback', message);
    },
    // 处理时间轴左右拖动
    handleChangeTime(item) {
      const changeObj = item.calcTime();
      const message = {
        messageType: 'changeTime',
        data: changeObj,
      };
      this.$emit('callback', message);
    },
    // 处理时间轴点击
    handleDbClick(item) {
      const { dbClick } = item.disable;
      if (!dbClick) {
        const { name, mark } = item;
        const message = {
          messageType: 'dbClick',
          data: {
            name,
            mark,
          },
        };
        this.$emit('callback', message);
      }
    },
    // 点击左侧图标
    handleClickIcon(item, clickType) {
      const { mark, name, type, status } = item.parseClickIcon(clickType);
      if (mark && name && type) {
        const message = {
          messageType: 'clickIcon',
          data: {
            mark,
            name,
            type,
            status,
          },
        };
        this.$emit('callback', message);
      }
    },
    // 点击元素
    handleClickItem(item) {
      const { clickItem } = item.disable;
      if (!clickItem) {
        const { name, mark } = item;
        const message = {
          messageType: 'clickItem',
          data: {
            name,
            mark,
          },
        };
        this.$emit('callback', message);
      }
    },
    // 点击右键菜单
    handleClickMenu(item, index, value) {
      const { mark, name } = item;
      const message = {
        messageType: 'clickMenu',
        data: {
          name,
          mark,
          index,
          value, // delete | deleteAll
        },
      };
      this.$emit('callback', message);
      item.handleClickMenu(index, value); // 这里处理一下删除多元素轴时自动向后移动
    },
    // 拖动同轴多元素的顺序
    handleChangeOrder(item) {
      const { mark, name, sliderArr } = item;
      const message = {
        messageType: 'changeOrder',
        data: { name, mark, sliderArr: JSON.parse(JSON.stringify(sliderArr)) },
      };
      this.$emit('callback', message);
    },
    // 选中的回调
    handleSelect(item, index) {
      this.changeSelectStatus(item, index);
      const { name, mark } = item;
      const message = {
        messageType: 'select',
        data: {
          name,
          mark,
          index,
        },
      };
      this.$emit('callback', message);
    },
    // 处理点击图标
    handleACTCallback() {
      const { timeLineArr } = this;
      timeLineArr.forEach(item => {
        item.changeTime = this.handleChangeTime.bind(this); // 单轴时间改变
        item.dbClick = this.handleDbClick.bind(this); // 单轴时间改变
        item.clickIcon = this.handleClickIcon.bind(this); // 点击左侧的图标
        item.clickItem = this.handleClickItem.bind(this); // 点击行内的元素
        item.clickMenu = this.handleClickMenu.bind(this); // 点击右键菜单
        item.changeOrder = this.handleChangeOrder.bind(this); // 同轴顺序改变
        item.select = this.handleSelect.bind(this); // 单个轴选中
        item.openPreview = this.handleOpenPreview.bind(this); // 打开预览弹窗
        item.closePreview = () => this.$refs.previewPopoverRef.closePopover(); // 打开预览弹窗
      });
    },
    // 处理外部拖拽结束
    handleDropEnd(e) {
      const message = {
        messageType: 'drop',
        data: e,
      };
      this.$emit('callback', message);
    },
    // 处理层次变化
    handleZIndexChange(zIndexArr) {
      const { timeLineArr } = this;
      zIndexArr.forEach(item => {
        const { mark, zIndex } = item;
        const el = timeLineArr.find(item => item.mark === mark);
        if (el) {
          el.zIndex = zIndex;
        }
      });
    },
    // 用来单独判断超写实虚拟人的
    handleVtuber(first, second) {
      // 这里判断一下first是不是超写实组件
      const { name, type } = first;
      if (name === 'vtuber' && (type === 'real' || type === 'normal')) {
        this.delVtuberConfig(second, type); // 如果是超写实虚拟人,先删除原有的动作
      }
    },
    // 用于查找并修改动作,背景,相机轴参数
    changeRealConfig(name, second) {
      const { timeLineArr, partUpdateArr } = this;
      const oldIndex = timeLineArr.findIndex(item => item.name === name);
      const oldEl = timeLineArr[oldIndex];
      const newEL = second.find(item => item.name === name);
      if (oldEl) {
        if (newEL) {
          this.extendBefore(newEL, oldEl);
          partUpdateArr.push(oldEl.mark);
        } else {
          timeLineArr.splice(oldIndex, 1);
        }
      } else if (!oldEl && newEL) {
        timeLineArr.push(newEL);
      }
    },
    // 处理时间轴缩放比例改变
    scaleRateChange(data) {
      const { scaleRate } = data;
      this.scaleRate = scaleRate;
      this.$refs.renderScaleRef.scaleRateChange(scaleRate);
      this.$refs.renderTimeLineRef.scaleRateChange(scaleRate);
      this.handleResize(); // 主动重绘事件
    },
    // 处理时间轴区域滚动同步
    scrollSync(scrollLeft) {
      this.$refs.renderScaleRef.scrollSync(scrollLeft);
    },
    // 清除之前选中并选中新的
    changeSelectStatus(newItem, index) {
      const { timeLineArr } = this;
      for (let i = 0; i < timeLineArr.length; i++) {
        const item = timeLineArr[i];
        if (item.currentSelect !== -1) {
          item.currentSelect = -1;
          break;
        }
      }
      newItem.currentSelect = index;
    },
    // 选中时间轴
    changeSelected(mark) {
      const marks = Array.isArray(mark) ? mark : [mark];
      const { timeLineArr } = this;
      for (let i = 0; i < timeLineArr.length; i++) {
        const item = timeLineArr[i];
        // 清空其他所有的选中
        if (marks.find(i => i === item.mark)) {
          item.currentSelect = 0;
          const message = {
            messageType: 'select',
            data: {
              name: item.name,
              mark: item.mark,
              index: i,
            },
          };
          this.$emit('callback', message);
        } else {
          item.currentSelect = -1;
        }
      }
    },
    // 用于局部更新,旧的轴继承新的轴的属性,指定那些属性需要继承
    extendBefore(newEl, oldEl) {
      const { partUpdateAttr } = newEl;
      const PART_UPDATE = [
        'iconDisable',
        'id',
        'startTime',
        'duration',
        'videoDuration',
        'blockArr',
        'fatherEl',
        'childrenElList',
        'backUpAttr',
        'sliderArrCallback',
        'allTime',
        'loop',
        'fatherStartTime',
        ...partUpdateAttr,
      ];
      PART_UPDATE.forEach(item => {
        if (oldEl.hasOwnProperty(item)) {
          if (item === 'iconDisable') {
            const { contract } = oldEl[item];
            oldEl[item] = newEl[item];
            oldEl[item].contract = contract;
          } else {
            oldEl[item] = newEl[item];
          }
        }
      });
    },
    handleOpenPreview(item, el, demoUrl) {
      this.$refs.previewPopoverRef.openPopover(item, el, demoUrl);
    },
    // 判断用户是否点击delete按键, 筛选到当前选择的项,并触发对应的删除事件
    clickDelete(e) {
      const { nodeName, contentEditable } = e.target;
      if (['INPUT', 'TEXTAREA'].indexOf(nodeName) > -1 || contentEditable === 'true') {
        return;
      }
      const CAN_DELETE_TIMELINE = ['bgMusic', 'background3d', 'camera', 'action', 'emotion'];
      // 目前只有3个轴需要使用捕获删除的方法
      const key = (e.key || '').toLowerCase();
      if (key === 'delete' || key === 'backspace') {
        const { timeLineArr } = this;
        const item = timeLineArr.find(
          i => i.currentSelect !== -1 && CAN_DELETE_TIMELINE.includes(i.name),
        );
        const canDelete = item?.contextMenuList?.find?.(item1 => item1 === 'delete');
        if (item && canDelete) {
          this.handleClickMenu(item, item.currentSelect, 'delete');
          e.stopPropagation();
        }
      }
    },
    async handleTextData(data) {
      const isMulti = isMultiVtuberByScene(data);
      if (isMulti) {
        return;
      }
      const { mode, textDriver } = data.broadcast;
      if (mode === 'text') {
        const { url } = textDriver;
        textDriver.voiceData = await getBlobByUrl(url);
      }
    },
  },
};
</script>

<style lang="stylus" scoped>
.time-line-com
  min-width 800px
  background-color $color-foreground-editor-primary
  display flex
  user-select none
  flex-direction column
  position relative
  padding 0 8px
  margin-top -4px
  height 100%
</style>
